/* eslint-disable @typescript-eslint/no-explicit-any */
import axios from 'axios';

import { message } from 'antd';

import {
    authorizationKey,
    authorizationValue,
    requestTimeout,
    tokenKey,
    tokenType,
    userTypeKey,
    userTypeValue,
} from 'Configs/common';
import rootStore from 'Core/RootStore';
import { WithCommonModelIndex } from 'Models/common/WithCommonModel';
import { EHttpMethod } from 'Typings/system';

import type { AxiosError, AxiosInstance, AxiosPromise, AxiosRequestConfig, Canceler } from 'axios';
import type WithCommonModel from 'Models/common/WithCommonModel';
import type { ApiItem } from 'Typings/system';

type RequestOption = {
    urlConfig?: UrlConfig;
    params?: unknown;
    data?: unknown;
    canceler?: (cancelerArg: Canceler) => void;
    axiosOpt?: AxiosRequestConfig<any>;
    reqParams?: any;
};
type RequestData = ApiItem & RequestOption;
type CreateAxios = (config?: AxiosRequestConfig) => AxiosInstance;
type RequestCall<T = any> = (data: RequestData) => AxiosPromise<CommonSysReponse<T>>;

const createAxios: CreateAxios = (extraConfig = {}) => {
    const instance = axios.create({
        timeout: requestTimeout,
        timeoutErrorMessage: 'code: timeout',
        ...extraConfig,
    });

    instance.interceptors.response.use(
        response => response,
        (error: AxiosError<CommonSysReponse>) => {
            if (error.toString() !== 'Cancel') {
                // eslint-disable-next-line no-console
                console.warn('interceptors error:', error);

                if (error.response?.data.msg) message.error(error.response?.data.msg, 5);
            }

            return Promise.reject(error);
        },
    );

    return instance;
};

const getUrlVal: (url: string, urlConfig?: UrlConfig) => string = (url, urlConfig) => {
    if (!!url && !!urlConfig) {
        let tmp = url;
        Object.keys(urlConfig).forEach(item => {
            tmp = tmp.replace(`[:${item}]`, `${urlConfig[item]}`);
        });

        return tmp;
    }

    return url;
};

const getCommonHeader: () => Record<string, string> = () => ({
    [tokenKey]: `${tokenType} ${rootStore?.authStore?.token ?? ''}`,
    [userTypeKey]: userTypeValue,
    [authorizationKey]: authorizationValue,
});

const put: RequestCall = ({ url = '', urlConfig, data = {}, canceler = () => {}, axiosOpt = {} }) =>
    createAxios({
        cancelToken: new axios.CancelToken(canceler),
        ...axiosOpt,
    }).put(
        getUrlVal(url, urlConfig),
        {
            ...(data as object),
        },
        {
            headers: {
                ...getCommonHeader(),
            },
        },
    );

const get: RequestCall = ({ url = '', urlConfig, params = {}, canceler = () => {}, axiosOpt = {} }) =>
    createAxios({
        cancelToken: new axios.CancelToken(canceler),
        ...axiosOpt,
    }).get(getUrlVal(url, urlConfig), {
        params,
        paramsSerializer: {
            encode: (toSerializer: Record<string, any>) => {
                // if (toSerializer) return queryString.stringify(toSerializer, queryStringStringifyOption);
                return toSerializer;
            },
        },
        headers: {
            ...getCommonHeader(),
        },
    });

const post: RequestCall = ({
    url = '',
    urlConfig,
    data = {},
    canceler = () => {},
    axiosOpt = {},
    reqParams = undefined,
}) =>
    createAxios({
        cancelToken: new axios.CancelToken(canceler),
        ...axiosOpt,
    }).post(getUrlVal(url, urlConfig), data, {
        headers: {
            ...getCommonHeader(),
            'Content-Type': 'application/json; charset=UTF-8',
        },
        params: reqParams,
    });

const postFormData: RequestCall = ({ url = '', urlConfig, data = {}, canceler = () => {}, axiosOpt = {} }) =>
    createAxios({
        cancelToken: new axios.CancelToken(canceler),
        ...axiosOpt,
    }).post(getUrlVal(url, urlConfig), data, {
        headers: {
            ...getCommonHeader(),
            'Content-Type': 'multipart/form-data',
        },
    });

const patch: RequestCall = ({
    url = '',
    urlConfig,
    data = {},
    canceler = () => {},
    axiosOpt = {},
    reqParams = undefined,
}) =>
    createAxios({
        cancelToken: new axios.CancelToken(canceler),
        ...axiosOpt,
    }).patch(
        getUrlVal(url, urlConfig),
        {
            ...(data as object),
        },
        {
            headers: {
                ...getCommonHeader(),
                'Content-Type': 'application/json; charset=UTF-8',
            },
            params: reqParams,
        },
    );

const del: RequestCall = ({ url = '', urlConfig, data = {}, canceler = () => {}, axiosOpt = {} }) =>
    createAxios({
        cancelToken: new axios.CancelToken(canceler),
        ...axiosOpt,
    }).delete(getUrlVal(url, urlConfig), {
        data,
        paramsSerializer: {
            encode: (toSerializer: Record<string, any>) => {
                // if (toSerializer) return queryString.stringify(toSerializer, queryStringStringifyOption);

                return toSerializer;
            },
        },
        headers: {
            ...getCommonHeader(),
        },
    });

const request = <T = any>(data: ApiItem, option: RequestOption): AxiosPromise<CommonSysReponse<T>> => {
    const { method, isForm } = data;

    switch (method) {
        case EHttpMethod.PUT:
            return put({ ...data, ...option });

        case EHttpMethod.GET:
            return get({ ...data, ...option });

        case EHttpMethod.POST:
            if (isForm) return postFormData({ ...data, ...option });
            return post({ ...data, ...option });

        case EHttpMethod.PATCH:
            return patch({ ...data, ...option });

        case EHttpMethod.DELETE:
            return del({ ...data, ...option });

        default:
            return get({ ...data, ...option });
    }
};

interface SelectorOptionsData<Model = any> extends OptionItem {
    data: Model;
    disabled?: boolean;
}
type GetSelectorOptionsData = <Model extends WithCommonModel>(
    data: Model[],
    statusFilter?: (key: Key) => boolean,
) => SelectorOptionsData<Model>[];
export const getSelectorOptionsData: GetSelectorOptionsData = (data, statusFilter) =>
    data.map(item => {
        const { [WithCommonModelIndex.ID]: id, [WithCommonModelIndex.NAME]: name } = item;

        const tmp: SelectorOptionsData = {
            data: item,
            value: id ?? '',
            label: name ?? '',
            disabled: statusFilter?.(id ?? '') ?? false,
        };

        return tmp;
    });

export default request;
